fix(session-log): walk ancestor PIDs to resolve correct session log#598
Merged
backnotprop merged 3 commits intobacknotprop:mainfrom Apr 21, 2026
Merged
Conversation
…data When plannotator is invoked from a slash command's `!` bang, the direct parent process is an intermediate bash shell spawned by the Bash tool — not Claude Code itself. The old resolveSessionLogByPpid() only checked process.ppid, so it always missed the session metadata file and fell back to mtime-based selection, which picks the wrong log when multiple sessions exist for the same project. New resolution ladder (four tiers): 1. Ancestor-PID walk: call `ps -o ppid=` repeatedly from process.ppid up to 8 hops, checking ~/.claude/sessions/<pid>.json at each hop. Deterministic — no guessing, matches exact session every time. 2. Cwd-scan: read every ~/.claude/sessions/*.json, filter by cwd, pick most recent startedAt. Handles cases where ps is unavailable. 3. CWD slug mtime (legacy): existing behavior, fragile with multiple sessions. 4. Ancestor directory walk: handles cd-deeper-into-subdirectory cases. Adds getAncestorPids (injectable getParent for testing), resolveSessionLogByAncestorPids, and resolveSessionLogByCwdScan. Exports SessionMetadata and accepts projectsDirOverride on findSessionLogsForCwd for test isolation. 17 new tests cover: edge cases for getAncestorPids (cycles, maxHops, self-loops), resolveSessionLogByAncestorPids (finds correct session among multiple, skips missing logs, falls back when no metadata matches), and resolveSessionLogByCwdScan (picks newest startedAt, ignores mismatched cwd, handles missing sessions dir). Closes backnotprop#458
393e4be to
e3e8f28
Compare
Owner
|
Hey thanks! testing |
Tier 1 used `ps`, which doesn't exist on Windows. Multiple Claude Code sessions in the same repo remained broken on Windows because tier 2 (cwd scan) can't disambiguate when both sessions share the same cwd. - Replace per-hop `ps` calls with a single process-table snapshot (`ps` on Unix, PowerShell Get-CimInstance on Windows), cached across the walk. One spawn instead of up to eight; faster on both platforms. - Export pure-function parsers (parseProcessTablePs, parseProcessTableCsv) so the Windows path is unit-testable without a Windows runner. - Normalize cwd comparison in resolveSessionLogByCwdScan: Windows is case-insensitive and processes may report drive letters in either case, so fold slashes and lowercase before comparing. Windows viability confirmed empirically: ~/.claude/sessions/<pid>.json exists with the expected schema on Windows 11 + Claude Code 2.1.116. For provenance purposes, this commit was AI assisted.
Owner
|
I've added Windows support. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #458
Problem
When plannotator is invoked from a slash command's
!bang, the direct parent process (process.ppid) is an intermediate bash shell spawned by the Bash tool — not Claude Code itself. The oldresolveSessionLogByPpid()only checkedprocess.ppid, so it always returnednullfor the session metadata lookup. The fallbackfindSessionLogsForCwd()then picks the most recently modified.jsonlfor the project, which is wrong when multiple sessions exist for the same repo.Empirically verified (as described in #458):
Fix
Four-tier resolution ladder, from most to least reliable:
resolveSessionLogByAncestorPids): callsps -o ppid=repeatedly fromprocess.ppid, walking up to 8 hops and checking~/.claude/sessions/<pid>.jsonat each one. Deterministic — matches the exact session without any guessing.resolveSessionLogByCwdScan): reads every~/.claude/sessions/*.json, filters bycwd, picks the entry with the most recentstartedAt. Handles environments wherepsis unavailable and degrades better than mtime when multiple sessions exist..jsonlfor the project slug. Fragile with multiple sessions, but kept as a fallback.cd'd into a subdirectory after session start.New exports (for test isolation)
getAncestorPids(startPid, maxHops, getParent)— injectablegetParentfor testing without real PIDsresolveSessionLogByAncestorPids(opts)— injectablesessionsDir,projectsDir,getParentPid,startPidresolveSessionLogByCwdScan(opts)— injectablesessionsDir,projectsDir,cwdSessionMetadataexported (was private)findSessionLogsForCwd(cwd, projectsDirOverride?)— optional override for test isolationTests
17 new tests in
session-log.test.ts:getAncestorPids: invalid startPid, no parent, walks chain, respects maxHops, breaks on cycles, breaks on self-loopsresolveSessionLogByAncestorPids: finds the correct session among multiple candidates, skips PIDs with no metadata, skips PIDs whose session log is missing, returns null when nothing matchesresolveSessionLogByCwdScan: picks the session with the neweststartedAtwhen multiple exist, ignores sessions with mismatched cwd, returns null when sessions dir is missingAll tests use isolated tmpdirs and injected
getParentPid— no real process tree or filesystem paths.